package com.lifengdi.qiankun.lucene;

import com.lifengdi.qiankun.common.lock.DistributedLock;
import com.lifengdi.qiankun.common.response.Page;
import com.lifengdi.qiankun.common.utils.ObjectToMapUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: 李锋镝
 * @date: 2020-06-30 11:15
 */
@Component
public class LuceneHandler {

    @Resource
    private Analyzer analyzer;

    @Resource
    private Directory directory;

    @Resource
    private DistributedLock distributedLock;

    @Value("${lucene.indexDir:#{null}}")
    private String indexDir;

    @Value("${lucene.lock.prefix:LuceneUpdateIndexKey_}")
    private String prefix;


    /**
     * 分页查询
     *
     * @param query    查询条件
     * @param sort     排序
     * @param pageNo   当前页
     * @param pageSize 每页数量
     * @param clazz    查询结果的实体类型
     * @return 分页后的结果
     * @throws IOException 如果查询结果超出{@link BooleanQuery#getMaxClauseCount()}的限制
     */
    public <T> Page<T> queryForPage(Query query, Sort sort, int pageNo, int pageSize, Class<T> clazz) throws IOException {
        return queryForPage(query, sort, pageNo, pageSize, clazz, null);
    }

    /**
     * 分页查询
     *
     * @param query    查询条件
     * @param sort     排序
     * @param pageNo   当前页
     * @param pageSize 每页数量
     * @param clazz    查询结果的实体类型
     * @return 分页后的结果
     * @throws IOException 如果查询结果超出{@link BooleanQuery#getMaxClauseCount()}的限制
     */
    public <T> Page<T> queryForPage(Query query, Sort sort, int pageNo, int pageSize, Class<T> clazz, List<String> assignFields) throws IOException {
        // 查询
        Page<Map> page = search(query, sort, pageNo, pageSize, assignFields);
        List<T> collect = page.getData().stream()
                .map(map -> ObjectToMapUtils.mapToObject(map, clazz))
                .collect(Collectors.toList());
        return new Page<>(pageNo, pageSize, page.getTotalCount(), collect);
    }


    /**
     * 分页查询
     *
     * @param query    查询条件
     * @param sort     排序
     * @param pageNo   当前页
     * @param pageSize 每页数量
     * @return 分页后的结果
     * @throws IOException 如果查询子语句超出{@link BooleanQuery#getMaxClauseCount()}的限制
     */
    public Page<Map> search(Query query, Sort sort, int pageNo, int pageSize) throws IOException {
        return search(query, sort, pageNo, pageSize, null);
    }


    /**
     * 分页查询
     *
     * @param query        查询条件
     * @param sort         排序
     * @param pageNo       当前页
     * @param pageSize     每页数量
     * @param assignFields 指定返回字段
     * @return 分页后的结果
     * @throws IOException 如果查询子语句超出{@link BooleanQuery#getMaxClauseCount()}的限制
     */
    public Page<Map> search(Query query, Sort sort, int pageNo, int pageSize, List<String> assignFields) throws IOException {
        int begin = pageSize * (pageNo - 1);
        int maxResult = begin + pageSize;

        boolean assign = !CollectionUtils.isEmpty(assignFields);

        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(directory));
//        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(FSDirectory.open(Paths.get("D:/home/tomcat_admin/advertisesite/index"))));
        TopDocs topDocs;
        if (Objects.isNull(sort)) {
            topDocs = indexSearcher.search(query, maxResult);
        } else {
            topDocs = indexSearcher.search(query, maxResult, sort);
        }
        int end = Math.min(maxResult, (int) topDocs.totalHits.value);
        // 遍历结果集
        List<Map> result = new ArrayList<>();
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (int i = begin; i < end; i++) {
            Document document = indexSearcher.doc(scoreDocs[i].doc);
            List<IndexableField> fields = document.getFields();
            Map<String, Object> entity = new HashMap<>();
            fields.forEach(f -> {
                String name = f.name();
                if (assign) {
                    if (assignFields.contains(name)) {
                        entity.put(name, f.stringValue());
                    }
                } else {
                    entity.put(name, f.stringValue());
                }
            });
            result.add(entity);
        }
        return new Page<>(pageNo, pageSize, topDocs.totalHits.value, result);
    }

    /**
     * 批量更新索引
     *
     * @param documentList 要更新的文档集合
     * @param uniqueField  文档中的唯一字段字段名，例如id
     */
    public void index(List<Document> documentList, String uniqueField) {
        if (CollectionUtils.isEmpty(documentList)) {
            return;
        }
        Objects.requireNonNull(uniqueField);
        try {
            indexDoc(documentList, uniqueField);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 批量更新索引
     *
     * @param documentList 要更新的文档集合
     * @param uniqueField  文档中的唯一字段字段名，例如id
     */
    private void indexDoc(List<Document> documentList, String uniqueField) throws InterruptedException, IOException {
        String key = prefix + indexDir;

        distributedLock.lock(key);
        // 创建索引写入配置
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
        // 创建索引写入对象
        try (IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig)) {
            for (Document d : documentList) {
                try {
                    indexWriter.updateDocument(new Term(uniqueField, d.get(uniqueField)), d);
                } catch (IllegalArgumentException e) {
                    indexWriter.deleteDocuments(new Term(uniqueField, d.get(uniqueField)));
                    indexWriter.updateDocument(new Term(uniqueField, d.get(uniqueField)), d);
                }
            }
            indexWriter.commit();
        } finally {
            distributedLock.unlock(key);
        }
    }

    /**
     * 更新索引
     *
     * @param document    要更新的文档
     * @param uniqueField 文档中的唯一字段字段名，例如id
     */
    public void index(Document document, String uniqueField) {
        if (Objects.isNull(document)) {
            return;
        }
        Objects.requireNonNull(uniqueField);
        try {
            indexDoc(document, uniqueField);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 更新索引
     *
     * @param document    要更新的文档
     * @param uniqueField 文档中的唯一字段字段名，例如id
     */
    private void indexDoc(Document document, String uniqueField) throws InterruptedException, IOException {
        String key = prefix + indexDir;

        distributedLock.lock(key);
        // 创建索引写入配置
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
        // 创建索引写入对象
        try (IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig)) {
            indexWriter.updateDocument(new Term(uniqueField, document.get(uniqueField)), document);
            indexWriter.commit();
        } finally {
            distributedLock.unlock(key);
        }
    }

    /**
     * 删除索引文档
     *
     * @param fieldName  文档中的字段名，例如id
     * @param fieldValue 值
     */
    public void deleteDoc(String fieldName, String fieldValue) throws InterruptedException, IOException {
        String key = prefix + indexDir;

        distributedLock.lock(key);
        // 创建索引写入配置
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
        // 创建索引写入对象
        try (IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig)) {
            indexWriter.deleteDocuments(new Term(fieldName, fieldValue));
            indexWriter.commit();
        } finally {
            distributedLock.unlock(key);
        }
    }

    /**
     * 对象转文档
     *
     * @param bean 对象
     * @return Document
     */
    public <T> Document objectToDoc(T bean) {
        Map<String, Object> objectToMap = ObjectToMapUtils.objectToMap(bean);
        Set<Map.Entry<String, Object>> entrySet = objectToMap.entrySet();
        Document document = new Document();
        entrySet.forEach(entity -> {
            Object value = entity.getValue();
            if (value instanceof Long) {
                document.add(new StoredField(entity.getKey(), (Long) value));
                document.add(new NumericDocValuesField(entity.getKey(), (Long) value));
            } else if (value instanceof Number) {
                document.add(new StringField(entity.getKey(), value.toString(), Field.Store.YES));
                document.add(new StoredField(entity.getKey(), value.toString()));
            } else {
                String str = Objects.nonNull(value) ? value.toString() : "";
                document.add(new StringField(entity.getKey(), str, Field.Store.YES));
                document.add(new StoredField(entity.getKey(), str));
            }
        });
        return document;
    }

}
